<template>
  <div class="chatgpt-layout">
    <!-- 侧边栏-->
    <aside
      :class="[
        'chatgpt-sidebar',
        { 'sidebar-hidden': isMobile && !sidebarVisible },
      ]"
    >
      <header class="sidebar-header">
        <div class="brand">
          <span class="brand-icon">🤖</span>
          <span class="brand-text">AI Chat</span>
        </div>
        <el-button
          class="new-chat-button"
          type="primary"
          size="small"
          plain
          @click="handleNewChat"
          :loading="isSending"
        >
          新对话
        </el-button>
      </header>

      <el-input
        v-model="searchKeyword"
        placeholder="搜索对话"
        prefix-icon="Search"
        clearable
        size="small"
        class="sidebar-search"
      />

      <div v-if="sessionListLoading" class="conversation-loading">
        <el-skeleton animated :rows="6" />
      </div>
      <div v-else-if="filteredSessions.length" class="conversation-list">
        <button
          v-for="session in filteredSessions"
          :key="session.id"
          class="conversation-item"
          :class="{ active: session.id === activeSessionId }"
          @click="selectSession(session.id)"
        >
          <div class="title-row">
            <span class="conversation-title">{{
              session.name || "未命名对话"
            }}</span>
            <div class="session-actions">
              <el-button
                class="conversation-rename"
                text
                type="primary"
                size="small"
                :icon="Edit"
                @click.stop="handleRenameSession(session)"
              />
              <el-button
                class="conversation-delete"
                text
                type="danger"
                size="small"
                :icon="Delete"
                @click.stop="handleDeleteSession(session)"
              />
            </div>
          </div>
          <div class="conversation-meta">
            <span>{{ formatSessionTime(session) }}</span>
            <span v-if="session.messageCount"
              >{{ session.messageCount }} 条消息</span
            >
          </div>
        </button>
      </div>
      <div v-else class="conversation-empty">
        <p>还没有会话，点击「新对话」开始吧！</p>
      </div>

      <footer class="sidebar-footer">
        <input
          ref="excelInputRef"
          type="file"
          class="hidden-file-input"
          accept=".xls,.xlsx,.csv"
          @change="handleExcelFileChange"
        />
        <el-button
          text
          size="small"
          @click="refreshSessions"
          :loading="isLoadingSessions"
        >
          刷新列表
        </el-button>
        <el-button text size="small" @click="handleOpenDataLabEntry">
          数据实验室
        </el-button>
        <el-button
          text
          size="small"
          :loading="isImporting"
          @click="handleImportClick"
        >
          导入Excel
        </el-button>
        <el-button text size="small" @click="showSettingsHint">
          设置
        </el-button>
      </footer>
    </aside>

    <div
      v-if="isMobile && sidebarVisible"
      class="mobile-sidebar-overlay"
      @click="sidebarVisible = false"
    ></div>

    <main class="chatgpt-main">
      <header class="chatgpt-header">
        <div class="header-titles">
          <h1>{{ headerTitle }}</h1>
          <p v-if="currentSession" class="subtitle">
            {{ formatHeaderSubtitle(currentSession) }}
          </p>
        </div>
        <div class="header-actions">
          <el-button
            v-if="isMobile"
            class="sidebar-toggle-button"
            circle
            size="small"
            :icon="Menu"
            @click="toggleSidebar"
          />
          <el-select
            v-model="localSelectedModel"
            placeholder="选择模型"
            size="small"
            class="model-select"
            :loading="loadingModels"
            @change="handleModelChange"
          >
            <el-option
              v-for="model in availableModels"
              :key="model.name"
              :label="model.name"
              :value="model.name"
            />
          </el-select>
          <el-button
            class="clear-chat-button"
            type="danger"
            size="small"
            text
            :disabled="!messages.length || isSending"
            @click="handleClearChat"
          >
            清空聊天
          </el-button>
          <el-button
            class="logout-button"
            type="primary"
            size="small"
            text
            @click="handleLogout"
          >
            退出登录
          </el-button>
        </div>
      </header>

      <el-tabs v-model="activeMainTab" class="main-tabs">
        <el-tab-pane label="聊天" name="chat">
          <section class="excel-panel">
            <input
              ref="excelQaInputRef"
              type="file"
              class="hidden-file-input"
              accept=".xlsx,.csv"
              @change="handleExcelQaFileChange"
            />
            <div class="excel-status-row">
              <div class="excel-status-text">
                <strong>数据集</strong>
                <el-select
                  v-model="selectedDatasetId"
                  placeholder="数据集"
                  size="small"
                  class="dataset-select"
                  :loading="datasetsLoading"
                  filterable
                  clearable
                  @change="handleDatasetChange"
                >
                  <el-option
                    v-for="item in datasets"
                    :key="item.id"
                    :label="item.name"
                    :value="item.id"
                  />
                </el-select>
                <span class="dataset-name">{{ currentExcelDisplayName }}</span>
              </div>
              <div class="excel-status-actions">
                <el-button
                  size="small"
                  :loading="datasetsLoading"
                  @click="openDatasetUploadDialog"
                >
                  上传 Excel
                </el-button>
                <el-button
                  size="small"
                  text
                  :loading="datasetsLoading"
                  @click="loadDatasets"
                >
                  刷新
                </el-button>
              </div>
            </div>
            <p class="excel-panel-tip">
              上传 Excel 文件后，您可以基于文件内容进行问答和数据分析
            </p>
            <div class="excel-question-row">
              <el-input
                v-model="excelQuestion"
                class="excel-question-input"
                placeholder="请输入 Excel 相关问题"
                clearable
                :disabled="!selectedDatasetId || isExcelAsking"
                @keydown.enter.exact.prevent="handleExcelAsk"
              />
              <el-button
                type="primary"
                size="small"
                :disabled="!canAskExcel"
                :loading="isExcelAsking"
                @click="handleExcelAsk"
              >
                提问 Excel
              </el-button>
              <el-button
                size="small"
                :disabled="!selectedDatasetId"
                @click="
                  () => {
                    chartDialogVisible = true;
                    fetchDatasetColumns(selectedDatasetId);
                  }
                "
              >
                生成图表
              </el-button>
            </div>
          </section>

          <section class="chatgpt-messages chat-messages" ref="messageListRef">
            <div v-if="messageListLoading" class="messages-loading">
              <el-skeleton animated :rows="6" />
            </div>
            <div v-else-if="!messages.length" class="empty-state">
              <h2>今天想聊些什么？</h2>
              <p>选择下面的示例，或直接输入你的问题。</p>
              <div class="suggestions">
                <button
                  v-for="(suggestion, index) in promptSuggestions"
                  :key="`suggestion-${index}`"
                  class="suggestion-card"
                  @click="handlePromptClick(suggestion)"
                >
                  {{ suggestion }}
                </button>
              </div>
            </div>

            <template v-else>
              <div
                v-for="message in messages"
                :key="message.id"
                class="message-row"
                :class="`message-${message.role}`"
              >
                <div class="message-avatar">
                  <span v-if="message.role === 'assistant'">AI</span>
                  <span v-else>你</span>
                </div>
                <div class="message-content">
                  <div
                    v-if="message.metadata?.source === 'excel'"
                    class="excel-message-tag"
                  >
                    基于 Excel：{{
                      message.metadata?.excelFileName || "未命名文件"
                    }}
                  </div>
                  <template v-if="message.metadata?.type === 'chart'">
                    <div class="message-placeholder">
                      此消息包含数据图表，请下载结果查看详情。
                    </div>
                  </template>
                  <template v-else-if="message.metadata?.type === 'table'">
                    <div class="message-placeholder">
                      此消息包含表格内容，当前界面暂不支持展示。
                    </div>
                  </template>
                  <template v-else>
                    <div
                      v-if="
                        (message.metadata?.streaming ||
                          message.metadata?.pending) &&
                        !message.content?.trim()
                      "
                      class="message-placeholder typing-placeholder"
                    >
                      AI 正在生成…
                    </div>
                    <div v-else v-html="renderMessage(message.content)"></div>
                  </template>
                  <div
                    v-if="message.ragContext && message.ragContext.length"
                    class="rag-summary"
                  >
                    引用了 {{ message.ragContext.length }} 条相关资料
                  </div>
                </div>
              </div>
              <div
                v-if="showGlobalStreamingPlaceholder"
                class="message-row message-assistant streaming-placeholder-row"
              >
                <div class="message-avatar">AI</div>
                <div class="message-content">
                  <div class="message-placeholder typing-placeholder">
                    <span>AI 正在思考</span>
                    <span class="typing-dots">
                      <span></span>
                      <span></span>
                      <span></span>
                    </span>
                  </div>
                </div>
              </div>
            </template>
          </section>

          <footer class="chatgpt-composer">
            <div class="composer-box">
              <el-input
                v-model="inputMessage"
                type="textarea"
                class="composer-input"
                :autosize="{ minRows: 1, maxRows: 6 }"
                placeholder="输入消息，按 Enter 发送"
                :disabled="isSending || loadingModels || !localSelectedModel"
                @keydown="handleComposerKeydown"
              />
              <div class="composer-actions">
                <span class="composer-hint"
                  >Enter 发送 · Shift + Enter 换行</span
                >
                <div class="composer-buttons">
                  <el-button
                    v-if="isStreaming"
                    type="default"
                    size="small"
                    @click="handleStopStream"
                  >
                    停止生成
                  </el-button>
                  <el-button
                    type="primary"
                    size="small"
                    :disabled="!canSend"
                    :loading="isSending"
                    @click="handleSend"
                  >
                    发送
                  </el-button>
                </div>
              </div>
            </div>
            <p class="composer-disclaimer">
              AI 可能会出错，请在重要场景中自行核对信息。
            </p>
          </footer>
        </el-tab-pane>

        <el-tab-pane label="数据实验室" name="dataLab">
          <DataLabPanel />
        </el-tab-pane>
      </el-tabs>
    </main>

    <el-drawer
      v-model="dataLabDrawerVisible"
      size="80%"
      custom-class="chat-data-lab-drawer"
      :destroy-on-close="true"
    >
      <template #header>
        <div class="drawer-header">
          <span>数据实验室</span>
          <div class="drawer-header__actions">
            <el-button text size="small" @click="goToDataLabPage"
              >在独立页面打开</el-button
            >
            <el-button text size="small" @click="closeDataLabDrawer"
              >关闭</el-button
            >
          </div>
        </div>
      </template>
      <DataLabPanel />
    </el-drawer>

    <el-dialog
      v-model="datasetDialogVisible"
      title="上传 Excel 文件"
      width="480px"
    >
      <el-upload
        class="dataset-uploader"
        drag
        :show-file-list="false"
        :http-request="handleDatasetUpload"
        accept=".xlsx,.xls,.csv"
        :disabled="datasetUploadLoading"
      >
        <i class="el-icon-upload"></i>
        <div class="el-upload__text">将文件拖到此处，或 <em>点击上传</em></div>
        <div class="el-upload__tip">支持 .xlsx/.xls/.csv</div>
      </el-upload>
    </el-dialog>

    <el-dialog
      v-model="chartDialogVisible"
      title="生成图表"
      width="640px"
      @opened="renderChart"
    >
      <el-form label-width="90px">
        <el-form-item label="图表类型">
          <el-select v-model="chartForm.chartType" size="small">
            <el-option label="折线图" value="line" />
            <el-option label="柱状图" value="bar" />
            <el-option label="饼图" value="pie" />
          </el-select>
        </el-form-item>
        <el-form-item label="X 轴字段">
          <el-select v-model="chartForm.xField" filterable size="small">
            <el-option
              v-for="col in chartColumns"
              :key="col"
              :label="col"
              :value="col"
            />
          </el-select>
        </el-form-item>
        <el-form-item label="Y 轴字段">
          <el-select v-model="chartForm.yField" filterable size="small">
            <el-option
              v-for="col in chartColumns"
              :key="col"
              :label="col"
              :value="col"
            />
          </el-select>
        </el-form-item>
        <el-form-item label="分组字段">
          <el-select
            v-model="chartForm.groupField"
            filterable
            clearable
            size="small"
          >
            <el-option
              v-for="col in chartColumns"
              :key="col"
              :label="col"
              :value="col"
            />
          </el-select>
        </el-form-item>
        <el-form-item>
          <el-button
            type="primary"
            :loading="chartGenerating"
            @click="handleGenerateChart"
            >生成</el-button
          >
        </el-form-item>
      </el-form>
      <div
        v-if="chartOption"
        ref="chartContainerRef"
        class="chart-preview"
      ></div>
      <div v-else class="chart-preview placeholder">图表预览区域</div>
    </el-dialog>
  </div>
</template>

<script setup lang="ts">
import {
  computed,
  nextTick,
  onActivated,
  onBeforeUnmount,
  onMounted,
  reactive,
  ref,
  watch,
} from "vue";
import { useRoute, useRouter } from "vue-router";
import { storeToRefs } from "pinia";
import { ElMessage, ElMessageBox } from "element-plus";
import { Delete, Edit, Menu } from "@element-plus/icons-vue";
import ollamaService, { type OllamaModel } from "@/services/ollamaService";
import excelService from "@/services/excelService";
import DataLabPanel from "@/components/DataLabPanel.vue";
import * as echarts from "echarts";
import { useChatStore } from "@/stores/chat";
import { useSessionStore } from "@/stores/session";
import type { Session } from "@/types/session";
import type { Message } from "@/types";
import sessionService from "@/services/sessionService";
import apiClient from "@/api/api";
import ErrorHandler from "@/utils/errorHandler";
import type { ExcelDataset } from "@/services/excelService";

const chatStore = useChatStore();
const sessionStore = useSessionStore();
const route = useRoute();
const router = useRouter();

const {
  messages,
  inputMessage,
  isSending,
  isStreaming,
  currentSessionId,
  currentModel,
  isHistoryLoading,
} = storeToRefs(chatStore);
const { sessions, currentSession, currentId, isLoadingSessions } =
  storeToRefs(sessionStore);

const searchKeyword = ref("");
const availableModels = ref<OllamaModel[]>([]);
const loadingModels = ref(false);
const messageListRef = ref<HTMLDivElement | null>(null);
const localSelectedModel = ref("");
const isMobile = ref(false);
const sidebarVisible = ref(true);
const excelInputRef = ref<HTMLInputElement | null>(null);
const excelQaInputRef = ref<HTMLInputElement | null>(null);
const isImporting = ref(false);
const excelQuestion = ref("");
const activeMainTab = ref<"chat" | "dataLab">("chat");
const isExcelUploading = ref(false);
const isExcelClearing = ref(false);
const isExcelAsking = ref(false);
const excelSessionStateMap = ref<Record<string, ExcelSessionState>>({});
const dataLabDrawerVisible = ref(false);
const activeSessionId = computed(
  () => currentId.value || currentSessionId.value
);
const headerTitle = computed(
  () => currentSession.value?.name?.trim() || "未命名对话"
);
const EXCEL_STATE_STORAGE_KEY = "excelSessionStateMap";
const datasets = ref<ExcelDataset[]>([]);
const datasetsLoading = ref(false);
const datasetDialogVisible = ref(false);
const datasetUploadLoading = ref(false);
const selectedDatasetId = ref("");
const chartDialogVisible = ref(false);
const chartGenerating = ref(false);
const chartForm = reactive({
  chartType: "line",
  xField: "",
  yField: "",
  groupField: "",
});
const chartColumns = ref<string[]>([]);
const chartOption = ref<any>(null);
const chartContainerRef = ref<HTMLDivElement | null>(null);

interface ExcelSessionState {
  currentExcelFileName: string | null;
  currentExcelFileId: string | null;
  clearedAt?: string | null;
}

const loadPersistedExcelState = (): Record<string, ExcelSessionState> => {
  if (typeof window === "undefined") {
    return {};
  }
  try {
    const raw = localStorage.getItem(EXCEL_STATE_STORAGE_KEY);
    if (!raw) {
      return {};
    }
    const parsed = JSON.parse(raw);
    return typeof parsed === "object" && parsed ? parsed : {};
  } catch (error) {
    console.warn("恢复 Excel 状态失败", error);
    return {};
  }
};

const persistExcelState = () => {
  if (typeof window === "undefined") {
    return;
  }
  try {
    localStorage.setItem(
      EXCEL_STATE_STORAGE_KEY,
      JSON.stringify(excelSessionStateMap.value)
    );
  } catch (error) {
    console.warn("保存 Excel 状态失败", error);
  }
};

const storedExcelState = loadPersistedExcelState();
if (Object.keys(storedExcelState).length) {
  excelSessionStateMap.value = storedExcelState;
}

const selectedModel = computed({
  get: () => currentModel.value || "",
  set: (value: string) => chatStore.setCurrentModel(value),
});

const filteredSessions = computed(() => {
  const keyword = searchKeyword.value.trim().toLowerCase();
  if (!keyword) {
    return sessions.value;
  }
  return sessions.value.filter((session) => {
    const title = session.name || "未命名对话";
    return title.toLowerCase().includes(keyword);
  });
});

const sessionListLoading = computed(
  () => isLoadingSessions.value && sessions.value.length === 0
);
const messageListLoading = computed(
  () => isHistoryLoading.value && messages.value.length === 0
);

const canSend = computed(() => {
  return (
    Boolean(inputMessage.value.trim()) &&
    !isSending.value &&
    Boolean(localSelectedModel.value)
  );
});

const currentExcelState = computed<ExcelSessionState>(() => {
  const sessionId = activeSessionId.value;
  if (!sessionId) {
    return {
      currentExcelFileName: null,
      currentExcelFileId: null,
      clearedAt: null,
    };
  }
  if (!excelSessionStateMap.value[sessionId]) {
    excelSessionStateMap.value = {
      ...excelSessionStateMap.value,
      [sessionId]: {
        currentExcelFileName: null,
        currentExcelFileId: null,
        clearedAt: null,
      },
    };
  }
  return excelSessionStateMap.value[sessionId];
});

const selectedDataset = computed(
  () =>
    datasets.value.find((item) => item.id === selectedDatasetId.value) || null
);
const currentExcelDisplayName = computed(
  () => selectedDataset.value?.name || "未选择数据集"
);

const canAskExcel = computed(() => {
  return (
    Boolean(excelQuestion.value.trim()) &&
    Boolean(selectedDatasetId.value) &&
    !isExcelAsking.value
  );
});

const promptSuggestions = [
  "帮我把这段文字总结成要点",
  "写一段用于职位招聘的自我介绍",
  "根据这份会议纪要，整理出行动计划",
  "请推荐一份适合初学者的学习路线",
];

const ensureExcelStateEntry = (sessionId: string) => {
  if (!sessionId) {
    return;
  }
  if (!excelSessionStateMap.value[sessionId]) {
    excelSessionStateMap.value = {
      ...excelSessionStateMap.value,
      [sessionId]: {
        currentExcelFileName: null,
        currentExcelFileId: null,
        clearedAt: null,
      },
    };
  }
};

const setExcelState = (
  sessionId: string,
  patch: Partial<ExcelSessionState>
) => {
  if (!sessionId) {
    return;
  }
  ensureExcelStateEntry(sessionId);
  const nextPatch: Partial<ExcelSessionState> = { ...patch };
  if (patch.currentExcelFileId) {
    nextPatch.clearedAt = null;
  }
  excelSessionStateMap.value = {
    ...excelSessionStateMap.value,
    [sessionId]: {
      ...excelSessionStateMap.value[sessionId],
      ...nextPatch,
    },
  };
};

const deriveExcelStateFromMessages = (sessionId: string) => {
  if (!sessionId || currentExcelState.value.currentExcelFileId) {
    return;
  }
  const state = excelSessionStateMap.value[sessionId];
  const clearedAtTs = state?.clearedAt
    ? new Date(state.clearedAt).getTime()
    : 0;
  const latestExcelMessage = messages.value
    .slice()
    .reverse()
    .find((message) => message?.metadata?.source === "excel");
  if (!latestExcelMessage) {
    return;
  }
  const messageTs = new Date(latestExcelMessage.timestamp || "").getTime();
  if (
    state?.clearedAt &&
    messageTs &&
    !Number.isNaN(clearedAtTs) &&
    messageTs <= clearedAtTs
  ) {
    return;
  }
  const meta = latestExcelMessage.metadata || {};
  const fileId = meta.excelFileId || meta.file_id || meta.fileId;
  if (!fileId) {
    return;
  }
  const fileName =
    meta.excelFileName || meta.file_name || meta.fileName || "Excel 文件";
  setExcelState(sessionId, {
    currentExcelFileId: fileId,
    currentExcelFileName: fileName,
    clearedAt: null,
  });
};

const createExcelMessageId = () => {
  if (
    typeof crypto !== "undefined" &&
    typeof crypto.randomUUID === "function"
  ) {
    return crypto.randomUUID();
  }
  return `excel_${Date.now()}_${Math.random().toString(16).slice(2)}`;
};

const resetExcelUploadInput = () => {
  if (excelQaInputRef.value) {
    excelQaInputRef.value.value = "";
  }
};

const handleResize = () => {
  if (typeof window === "undefined") {
    return;
  }
  const mobile = window.innerWidth < 960;
  const previous = isMobile.value;
  isMobile.value = mobile;
  if (!mobile) {
    sidebarVisible.value = true;
  } else if (mobile && !previous) {
    sidebarVisible.value = false;
  }
};

const toggleSidebar = () => {
  sidebarVisible.value = !sidebarVisible.value;
};

const handleImportClick = () => {
  excelInputRef.value?.click();
};

const handleLogout = async () => {
  try {
    await apiClient.post("/v1/auth/logout");
  } catch (error) {
    console.warn("退出登录请求失败:", error);
  } finally {
    localStorage.removeItem("authToken");
    sessionStore.setCurrentSession(null);
    ElMessage.success("已退出登录");
    router.replace({ path: "/login" });
  }
};

const handleExcelFileChange = async (event: Event) => {
  const target = event.target as HTMLInputElement;
  const file = target.files?.[0];
  if (!file) {
    return;
  }
  isImporting.value = true;
  try {
    const result = await sessionService.importExcel(file);
    const importedCount =
      result?.importedCount ?? result?.sessions?.length ?? 0;
    ElMessage.success(`成功导入 ${importedCount} 个会话`);
    await sessionStore.loadSessions();
  } catch (error: any) {
    console.error("导入Excel失败:", error);
    const message = error?.message ?? "导入Excel失败，请稍后再试";
    ElMessage.error(message);
  } finally {
    isImporting.value = false;
    if (excelInputRef.value) {
      excelInputRef.value.value = "";
    }
  }
};

const triggerExcelUpload = () => {
  if (!activeSessionId.value) {
    ElMessage.warning("请先选择一个会话，再上传 Excel 文件");
    return;
  }
  excelQaInputRef.value?.click();
};

const handleExcelQaFileChange = async (event: Event) => {
  const target = event.target as HTMLInputElement;
  const file = target.files?.[0];
  if (!file) {
    return;
  }

  const name = file.name || "";
  const lower = name.toLowerCase();
  const allow = lower.endsWith(".xlsx") || lower.endsWith(".csv");
  if (!allow) {
    ElMessage.error("仅支持上传 .xlsx 或 .csv 文件");
    resetExcelUploadInput();
    return;
  }

  const sessionId = activeSessionId.value;
  if (!sessionId) {
    ElMessage.error("请先选择一个会话，再上传 Excel 文件");
    resetExcelUploadInput();
    return;
  }

  isExcelUploading.value = true;
  try {
    const response = await excelService.uploadFile(file, sessionId);
    const fileId = response.file_id || response.fileId || "";
    const fileName = response.file_name || response.fileName || file.name;
    if (!fileId) {
      throw new Error("后端未返回文件ID");
    }
    setExcelState(sessionId, {
      currentExcelFileId: fileId,
      currentExcelFileName: fileName,
    });
    ElMessage.success(`Excel 上传成功：${fileName}`);
  } catch (error: any) {
    console.error("Excel 上传失败", error);
    const detail = error?.response?.data?.detail || error?.message;
    ElMessage.error(
      `上传失败：${detail || "请检查文件格式是否为 .xlsx 或 .csv"}`
    );
  } finally {
    isExcelUploading.value = false;
    resetExcelUploadInput();
  }
};

const handleExcelClear = async () => {
  const sessionId = activeSessionId.value;
  if (!sessionId) {
    ElMessage.warning("请先选择一个会话");
    return;
  }
  if (!currentExcelState.value.currentExcelFileId) {
    ElMessage.info("当前会话未绑定 Excel 文件");
    return;
  }
  isExcelClearing.value = true;
  try {
    await excelService.clearExcel(sessionId);
    setExcelState(sessionId, {
      currentExcelFileId: null,
      currentExcelFileName: null,
      clearedAt: new Date().toISOString(),
    });
    ElMessage.success("已清除 Excel 文件");
  } catch (error: any) {
    console.error("清除 Excel 文件失败", error);
    const detail = error?.response?.data?.detail || error?.message;
    ElMessage.error(`清除 Excel 文件失败：${detail || "请稍后重试"}`);
  } finally {
    isExcelClearing.value = false;
  }
};

const handleExcelAsk = async () => {
  const sessionId = activeSessionId.value;
  const question = excelQuestion.value.trim();
  const datasetId = selectedDatasetId.value;

  if (!sessionId) {
    ElMessage.warning("请先选择一个会话");
    return;
  }
  if (!datasetId) {
    ElMessage.warning("请先选择或上传一个数据集");
    return;
  }
  if (!question) {
    ElMessage.warning("请输入要提问的内容");
    return;
  }

  const timestamp = new Date().toISOString();
  const datasetName = selectedDataset.value?.name || "Excel 数据集";
  const baseMetadata = {
    source: "excel",
    datasetId,
    datasetName,
  };
  const userMessage: Message = {
    id: createExcelMessageId(),
    role: "user",
    content: question,
    timestamp,
    metadata: baseMetadata,
  };
  chatStore.addMessage(sessionId, userMessage);

  isExcelAsking.value = true;
  try {
    const response = await excelService.askDataset(datasetId, question);
    const assistantMessage: Message = {
      id: createExcelMessageId(),
      role: "assistant",
      content: response.answer || "",
      timestamp: new Date().toISOString(),
      metadata: { ...baseMetadata },
    };
    chatStore.addMessage(sessionId, assistantMessage);
    excelQuestion.value = "";
    await sessionStore.loadSessions();
    ElMessage.success("Excel 问答完成");
  } catch (error: any) {
    console.error("Excel 问答失败", error);
    const detail =
      error?.response?.data?.message ||
      error?.response?.data?.detail ||
      error?.message;
    const timeoutError =
      error?.code === "ECONNABORTED" ||
      /timeout/i.test(String(error?.message || ""));
    if (timeoutError) {
      ElMessage.error(
        "Excel 问答超时，数据量较大时可能需要更长时间，请简化问题或稍后重试"
      );
    } else {
      ElMessage.error(`Excel 问答失败：${detail || "请检查数据集并重试"}`);
    }
  } finally {
    isExcelAsking.value = false;
  }
};

const loadDatasets = async () => {
  datasetsLoading.value = true;
  try {
    const response = await excelService.listDatasets({ page: 1, size: 100 });
    datasets.value = response.items || [];
    if (!selectedDatasetId.value && datasets.value.length) {
      selectedDatasetId.value = datasets.value[0].id;
      await fetchDatasetColumns(selectedDatasetId.value);
    }
  } catch (error) {
    console.error("加载数据集失败", error);
    ElMessage.error("加载数据集失败");
  } finally {
    datasetsLoading.value = false;
  }
};

const openDatasetUploadDialog = () => {
  datasetDialogVisible.value = true;
};

const handleDatasetUpload = async (options: any) => {
  const rawFile = (options?.file && (options.file.raw as File)) || (options?.file as File)
  const file = rawFile as File
  if (!file) {
    options?.onError?.(new Error('未选择文件'))
    return
  }
  datasetUploadLoading.value = true
  try {
    await excelService.uploadDataset(file)
    options?.onSuccess?.({}, file)
    ElMessage.success('Excel 导入成功')
    datasetDialogVisible.value = false
    await loadDatasets()
  } catch (error: any) {
    console.error('导入数据集失败', error)
    const detail = error?.response?.data?.message || error?.message
    ElMessage.error(detail || '导入失败')
    options?.onError?.(error)
  } finally {
    datasetUploadLoading.value = false
  }
}

const fetchDatasetColumns = async (datasetId: string) => {
  if (!datasetId) {
    chartColumns.value = [];
    return;
  }
  try {
    const preview = await excelService.previewDataset(datasetId, 5);
    chartColumns.value = preview.columns || [];
  } catch (error) {
    console.warn("获取字段列表失败", error);
    chartColumns.value = [];
  }
};

const handleDatasetChange = async (datasetId: string) => {
  selectedDatasetId.value = datasetId;
  await fetchDatasetColumns(datasetId);
};

const handleGenerateChart = async () => {
  const datasetId = selectedDatasetId.value;
  if (!datasetId) {
    ElMessage.warning("请先选择数据集");
    return;
  }
  if (!chartForm.xField || !chartForm.yField) {
    ElMessage.warning("请选择 X 轴和 Y 轴字段");
    return;
  }
  chartGenerating.value = true;
  try {
    const resp = await excelService.chartDataset(datasetId, {
      chart_type: chartForm.chartType,
      x_field: chartForm.xField,
      y_field: chartForm.yField,
      group_field: chartForm.groupField || undefined,
    });
    chartOption.value = resp.option;
    chartDialogVisible.value = true;
    await nextTick();
    renderChart();
  } catch (error: any) {
    console.error("生成图表失败", error);
    const detail = error?.response?.data?.message || error?.message;
    ElMessage.error(detail || "生成图表失败");
  } finally {
    chartGenerating.value = false;
  }
};

const renderChart = () => {
  if (!chartDialogVisible.value || !chartOption.value) return;
  const el = chartContainerRef.value;
  if (!el) return;
  const instance = echarts.getInstanceByDom(el) || echarts.init(el);
  instance.setOption(chartOption.value, true);
};

const escapeHtml = (value: string) => {
  return value
    .replace(/&/g, "&amp;")
    .replace(/</g, "&lt;")
    .replace(/>/g, "&gt;")
    .replace(/"/g, "&quot;")
    .replace(/'/g, "&#39;");
};

const renderMessage = (raw: string) => {
  if (!raw) {
    return "";
  }

  const blockPlaceholders: string[] = [];
  let content = raw.replace(/```([\s\S]*?)```/g, (_match, code) => {
    const index = blockPlaceholders.length;
    blockPlaceholders.push(
      `<pre><code>${escapeHtml(code.trim())}</code></pre>`
    );
    return `@@BLOCK_${index}@@`;
  });

  const inlinePlaceholders: string[] = [];
  content = content.replace(/`([^`]+)`/g, (_match, code) => {
    const index = inlinePlaceholders.length;
    inlinePlaceholders.push(`<code>${escapeHtml(code.trim())}</code>`);
    return `@@INLINE_${index}@@`;
  });

  content = escapeHtml(content)
    .replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>")
    .replace(/\*(.+?)\*/g, "<em>$1</em>")
    .replace(
      /\[(.+?)\]\((https?:\/\/[^\s]+)\)/g,
      '<a href="$2" target="_blank" rel="noopener noreferrer">$1</a>'
    )
    .replace(/\n/g, "<br />");

  content = content.replace(/@@INLINE_(\d+)@@/g, (_match, index) => {
    return inlinePlaceholders[Number(index)] ?? "";
  });

  content = content.replace(/@@BLOCK_(\d+)@@/g, (_match, index) => {
    return blockPlaceholders[Number(index)] ?? "";
  });

  return content;
};

const scrollToBottom = () => {
  nextTick(() => {
    const container = messageListRef.value;
    if (container) {
      container.scrollTop = container.scrollHeight;
    }
  });
};

const normalizeRouteParam = (value: unknown): string => {
  if (Array.isArray(value)) {
    return value[0] ?? "";
  }
  return typeof value === "string" ? value : "";
};

const getRouteSessionId = (): string => {
  return normalizeRouteParam(route.params.id);
};

const syncSessionState = async (
  sessionId: string,
  options: { force?: boolean } = {}
) => {
  const normalizedId = sessionId?.trim();
  if (!normalizedId) {
    return;
  }
  const isSameSession = normalizedId === activeSessionId.value;
  if (!options.force && isSameSession) {
    return;
  }
  const shouldReset = Boolean(options.force || !isSameSession);
  try {
    const matchedSession = sessions.value.find(
      (session) => session.id === normalizedId
    );
    const titleForEnsure =
      matchedSession?.name || currentSession.value?.name || undefined;
    await sessionStore.ensureSessionExists(normalizedId, titleForEnsure);
    sessionStore.setCurrentSession(normalizedId);
    chatStore.setActiveSession(normalizedId, { resetMessages: shouldReset });
    if (shouldReset) {
      chatStore.clearMessages(normalizedId);
    }
    await chatStore.loadHistoryMessages(normalizedId, {
      force: shouldReset,
      title: titleForEnsure,
    });
  } catch (error) {
    console.error("同步会话失败:", error);
  }
};

const normalizeIsoString = (input?: string) => {
  if (!input) {
    return "";
  }
  let normalized = input.trim();
  if (!normalized.includes("T") && normalized.includes(" ")) {
    normalized = normalized.replace(" ", "T");
  }
  const hasTimezone = /[zZ]|[+-]\d{2}:?\d{2}$/.test(normalized);
  return hasTimezone ? normalized : `${normalized}Z`;
};

const formatRelativeTime = (iso?: string) => {
  const normalized = normalizeIsoString(iso);
  if (!normalized) {
    return "";
  }
  const target = new Date(normalized);
  if (Number.isNaN(target.getTime())) {
    return "";
  }

  const diff = Date.now() - target.getTime();
  const minutes = Math.floor(diff / 60000);
  if (minutes < 1) {
    return "刚刚";
  }
  if (minutes < 60) {
    return `${minutes} 分钟前`;
  }
  const hours = Math.floor(minutes / 60);
  if (hours < 24) {
    return `${hours} 小时前`;
  }
  const days = Math.floor(hours / 24);
  if (days < 7) {
    return `${days} 天前`;
  }
  return target.toLocaleDateString("zh-CN", {
    month: "numeric",
    day: "numeric",
  });
};

const formatSessionTime = (session: Session) => {
  return formatRelativeTime(session.lastMessageTime || session.startTime);
};

const formatHeaderSubtitle = (session: Session) => {
  const pieces: string[] = [];
  if (session.messageCount) {
    pieces.push(`${session.messageCount} 条消息`);
  }
  const time = formatSessionTime(session);
  if (time) {
    pieces.push(time);
  }
  return pieces.join(" · ");
};

const handlePromptClick = (suggestion: string) => {
  inputMessage.value = suggestion;
};

const handleComposerKeydown = (event: KeyboardEvent) => {
  if (event.key === "Enter" && !event.shiftKey) {
    event.preventDefault();
    handleSend();
  }
};

const handleSend = async () => {
  if (!canSend.value) {
    return;
  }
  try {
    const modelName = localSelectedModel.value || selectedModel.value;
    await chatStore.sendMessage(modelName);
    scrollToBottom();
    await sessionStore.loadSessions();
  } catch (error) {
    ErrorHandler.handleError(error, "发送消息", "auto");
  }
};

const handleNewChat = async () => {
  try {
    const session = await sessionStore.createSession({ title: "新对话" });
    inputMessage.value = "";
    await router.push({ name: "chat", params: { id: session.id } });
    await syncSessionState(session.id, { force: true });
    scrollToBottom();
  } catch (error) {
    ErrorHandler.handleError(error, "创建新对话", "auto");
  }
};

const selectSession = async (sessionId: string) => {
  const normalizedId = sessionId?.trim();
  if (!normalizedId) {
    return;
  }
  // 先设置当前会话，再进行路由跳转
  sessionStore.setCurrentSession(normalizedId);
  const currentRouteId = getRouteSessionId();
  if (currentRouteId !== normalizedId) {
    await router.push({ name: "chat", params: { id: normalizedId } });
  } else {
    await syncSessionState(normalizedId, { force: true });
  }
  if (isMobile.value) {
    sidebarVisible.value = false;
  }
};

const refreshSessions = async () => {
  try {
    await sessionStore.loadSessions();
  } catch (error) {
    ErrorHandler.handleError(error, "刷新会话", "auto");
  }
};

const openDataLabDrawer = () => {
  dataLabDrawerVisible.value = true;
};

const closeDataLabDrawer = () => {
  dataLabDrawerVisible.value = false;
};

const goToDataLabPage = () => {
  router.push({ name: "dataLab" });
};

const showSettingsHint = () => {
  ElMessage.info("设置功能即将上线");
};

const hasStreamingMessage = computed(() => {
  return messages.value.some((message) => Boolean(message.metadata?.streaming));
});
const showGlobalStreamingPlaceholder = computed(() => {
  return isStreaming.value && !hasStreamingMessage.value;
});

const handleStopStream = () => {
  chatStore.stopStreaming();
  ErrorHandler.showInfo("已尝试停止生成", "流式响应");
};

const handleClearChat = async () => {
  const sessionId = currentSessionId.value;
  if (!sessionId) {
    ErrorHandler.showInfo("请选择要清空的会话", "清空聊天");
    return;
  }
  if (!messages.value.length) {
    ErrorHandler.showInfo("当前会话没有消息", "清空聊天");
    return;
  }

  try {
    await ElMessageBox.confirm(
      "确定要清空当前会话的所有聊天记录吗？这将删除当前会话并新建一个空会话。",
      "清空聊天",
      {
        type: "warning",
        confirmButtonText: "清空",
        cancelButtonText: "取消",
      }
    );
  } catch (error) {
    if (error === "cancel" || error === "close") {
      return;
    }
    ErrorHandler.handleError(error, "清空聊天", "auto");
    return;
  }

  try {
    await sessionStore.deleteSession(sessionId);
    chatStore.clearMessages(sessionId);
    await sessionStore.loadSessions();
    const replacement = await sessionStore.createSession({ title: "新对话" });
    await router.push({ name: "chat", params: { id: replacement.id } });
    await syncSessionState(replacement.id, { force: true });
    ErrorHandler.showSuccess("聊天记录已清空", "清空聊天");
  } catch (error) {
    ErrorHandler.handleError(error, "清空聊天", "auto");
  }
};

interface SetModelOptions {
  syncBackend?: boolean;
  toast?: boolean;
}

const setSelectedModel = async (
  modelName: string,
  options: SetModelOptions = {}
) => {
  if (!modelName) {
    return;
  }
  const previous = selectedModel.value;
  selectedModel.value = modelName;
  localSelectedModel.value = modelName;

  const { syncBackend = true, toast = true } = options;
  if (!syncBackend || modelName === previous) {
    return;
  }

  try {
    await ollamaService.switchModel(modelName);
    if (toast) {
      ErrorHandler.showSuccess(`已切换至模型 ${modelName}`, "模型切换");
    }
  } catch (error) {
    console.error("切换模型失败:", error);
    selectedModel.value = previous;
    localSelectedModel.value = previous;
    ErrorHandler.handleError(error, "切换模型", "auto");
    throw error;
  }
};

const handleModelChange = async (modelName: string) => {
  if (!modelName) {
    return;
  }
  try {
    await setSelectedModel(modelName, { syncBackend: true, toast: true });
  } catch {
    // 具体错误在 setSelectedModel 内部提示
  }
};

const loadModels = async () => {
  loadingModels.value = true;
  try {
    const models = await ollamaService.getModels();
    availableModels.value = models;
    if (!models.length) {
      localSelectedModel.value = "";
      selectedModel.value = "";
      return;
    }

    const original = selectedModel.value;
    let candidate = original;
    let usedRemote = false;

    if (!candidate || !models.some((model) => model.name === candidate)) {
      try {
        const remote = await ollamaService.getCurrentModel(
          currentSessionId.value
        );
        if (
          remote?.model &&
          models.some((model) => model.name === remote.model)
        ) {
          candidate = remote.model;
          usedRemote = true;
        }
      } catch (error) {
        console.warn("获取当前模型失败，使用默认模型", error);
      }
    }

    if (!candidate || !models.some((model) => model.name === candidate)) {
      candidate = models[0].name;
    }

    await setSelectedModel(candidate, {
      syncBackend: !usedRemote,
      toast: false,
    });
  } catch (error) {
    console.error("加载模型列表失败:", error);
    availableModels.value = [];
    const message = error instanceof Error ? error.message : "加载模型列表失败";
    ErrorHandler.handleError(error, "加载模型列表", "auto");
  } finally {
    loadingModels.value = false;
  }
};

const handleDeleteSession = async (session: Session) => {
  if (!session?.id) {
    return;
  }
  try {
    await ElMessageBox.confirm(
      "确定要删除该会话及其所有历史消息吗？此操作无法恢复。",
      "删除会话",
      {
        confirmButtonText: "删除",
        cancelButtonText: "取消",
        type: "warning",
      }
    );
  } catch {
    return;
  }

  try {
    const deletingActive = activeSessionId.value === session.id;
    await sessionStore.deleteSession(session.id);
    if (excelSessionStateMap.value[session.id]) {
      const nextState = { ...excelSessionStateMap.value };
      delete nextState[session.id];
      excelSessionStateMap.value = nextState;
    }
    await sessionStore.loadSessions();

    if (deletingActive) {
      chatStore.clearMessages(session.id);
      chatStore.setActiveSession("", { resetMessages: true });
      const fallbackId = currentId.value || sessions.value[0]?.id || "";
      if (fallbackId) {
        await router.push({ name: "chat", params: { id: fallbackId } });
        await syncSessionState(fallbackId, { force: true });
      } else {
        sessionStore.setCurrentSession(null);
        await router.push({ name: "chat" });
      }
    }

    ElMessage.success("会话已删除");
  } catch (error) {
    console.error("删除会话失败:", error);
    ElMessage.error("删除会话失败，请稍后再试");
  }
};

const handleRenameSession = async (session: Session) => {
  if (!session?.id) {
    return;
  }
  try {
    const { value } = await ElMessageBox.prompt(
      "输入新的会话标题",
      "重命名会话",
      {
        inputValue: session.name || "未命名对话",
        inputValidator: (val: string) => (!!val.trim() ? true : "标题不能为空"),
        confirmButtonText: "保存",
        cancelButtonText: "取消",
      }
    );
    const newTitle = value.trim();
    await sessionStore.renameSession(session.id, newTitle);
    await sessionStore.loadSessions();
    if (session.id === currentSessionId.value) {
      await syncSessionState(session.id, { force: true });
    }
    ElMessage.success("会话标题已更新");
  } catch (error) {
    if (error === "cancel" || error === "close") {
      return;
    }
    ErrorHandler.handleError(error, "重命名会话", "auto");
  }
};

watch(
  excelSessionStateMap,
  () => {
    persistExcelState();
  },
  { deep: true }
);

watch(
  messages,
  () => {
    scrollToBottom();
    const sessionId = activeSessionId.value;
    if (sessionId) {
      deriveExcelStateFromMessages(sessionId);
    }
  },
  { deep: true }
);

watch(activeSessionId, (next) => {
  if (next) {
    ensureExcelStateEntry(next);
  }
  excelQuestion.value = "";
});

// 路由驱动的会话切换
watch(
  () => route.params.id,
  async (param) => {
    const nextId = normalizeRouteParam(param);
    if (!nextId) {
      sessionStore.setCurrentSession(null);
      chatStore.setActiveSession("", { resetMessages: true });
      return;
    }
    await syncSessionState(nextId);
  },
  { immediate: true }
);

watch(currentModel, (model) => {
  localSelectedModel.value = model || "";
});

watch(selectedDatasetId, (id) => {
  if (id) {
    fetchDatasetColumns(id);
  }
});

watch(chartDialogVisible, (visible) => {
  if (visible) {
    nextTick(() => renderChart());
  }
});

onMounted(async () => {
  handleResize();
  window.addEventListener("resize", handleResize);
  try {
    await sessionStore.loadSessions();
    let initialId = getRouteSessionId();
    if (!initialId) {
      const fallbackId = currentId.value || sessions.value[0]?.id;
      if (fallbackId) {
        await router.replace({ name: "chat", params: { id: fallbackId } });
        initialId = fallbackId;
      }
      // 移除自动创建会话逻辑，确保只在点击新建对话时创建
    }
    if (initialId) {
      await syncSessionState(initialId, { force: true });
    }
  } catch (error) {
    console.error("加载会话列表失败:", error);
  }
  await loadDatasets();
  await loadModels();
  scrollToBottom();
});

onActivated(async () => {
  const routeId = getRouteSessionId();
  if (routeId && routeId !== activeSessionId.value) {
    await syncSessionState(routeId, { force: true });
  }
});

onBeforeUnmount(() => {
  window.removeEventListener("resize", handleResize);
});
</script>

<style scoped>
.chatgpt-layout {
  display: flex;
  height: 100vh;
  background: #f5f5f7;
  color: #1f2933;
  overflow: hidden;
}

.chatgpt-sidebar {
  width: 280px;
  background: #202123;
  color: #e5e7eb;
  display: flex;
  flex-direction: column;
  border-right: 1px solid #2a2b32;
  padding: 16px 14px;
  transition: all 0.3s ease;
}

.sidebar-hidden {
  display: none;
}

.mobile-sidebar-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.5);
  z-index: 15;
}

.sidebar-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 16px;
  gap: 12px;
}

.brand {
  display: flex;
  align-items: center;
  gap: 8px;
  font-weight: 600;
  font-size: 16px;
}

.brand-icon {
  font-size: 20px;
}

.new-chat-button {
  border-color: #3f83f8;
  color: #3f83f8;
  background-color: transparent;
}

.sidebar-search {
  margin-bottom: 12px;
}

.hidden-file-input {
  display: none;
}

:deep(.sidebar-search .el-input__wrapper) {
  background: #353740;
  border: none;
  box-shadow: none;
}

:deep(.sidebar-search .el-input__inner) {
  color: #f9fafb;
}

.conversation-list {
  flex: 1;
  overflow-y: auto;
  padding-right: 4px;
}

.conversation-item {
  width: 100%;
  border: none;
  background: transparent;
  color: inherit;
  text-align: left;
  padding: 12px 10px;
  border-radius: 10px;
  cursor: pointer;
  display: flex;
  flex-direction: column;
  gap: 6px;
  transition: background 0.2s ease, transform 0.2s ease;
}

.conversation-item:hover {
  background: #2a2b32;
  transform: translateX(2px);
}

.conversation-item.active {
  background: #343541;
  border: 1px solid rgba(59, 130, 246, 0.35);
}

.title-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}

.conversation-title {
  font-size: 14px;
  font-weight: 500;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.session-actions {
  display: flex;
  gap: 4px;
}

.conversation-delete,
.conversation-rename {
  opacity: 0;
}

.conversation-item:hover .conversation-delete,
.conversation-item:hover .conversation-rename {
  opacity: 1;
}

.conversation-meta {
  display: flex;
  gap: 10px;
  font-size: 12px;
  color: #9ca3af;
}

.conversation-empty {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 24px;
  color: #d1d5db;
  font-size: 13px;
}

.conversation-loading,
.messages-loading {
  padding: 12px 0;
}

.sidebar-footer {
  margin-top: 16px;
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: 12px;
}

.sidebar-footer .el-button {
  flex: 1 1 45%;
}

.chatgpt-main {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-height: 0;
  background: #ffffff;
}

.main-tabs {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-height: 0;
  padding: 0 24px 24px;
}

.main-tabs :deep(.el-tabs__header) {
  margin: 0 0 12px;
}

.main-tabs :deep(.el-tabs__content) {
  flex: 1;
  display: flex;
  min-height: 0;
}

.main-tabs :deep(.el-tab-pane) {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-height: 0;
  gap: 16px;
}

.chatgpt-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 24px 32px;
  border-bottom: 1px solid #e5e7eb;
  background: #ffffff;
}

.header-titles h1 {
  font-size: 20px;
  margin: 0 0 6px 0;
  color: #111827;
}

.subtitle {
  margin: 0;
  font-size: 13px;
  color: #6b7280;
}

.model-select {
  min-width: 180px;
}

.sidebar-toggle-button {
  margin-right: 8px;
}

.chatgpt-messages {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  background: #f7f8fc;
}

.excel-panel {
  padding: 12px 32px 0;
  border-bottom: 1px solid #e5e7eb;
  background: #ffffff;
}

.excel-status-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
  gap: 12px;
}

.excel-status-text {
  font-size: 14px;
  color: #374151;
}

.excel-status-actions {
  display: flex;
  gap: 8px;
  align-items: center;
}

.excel-panel-tip {
  margin: 6px 0 12px;
  font-size: 12px;
  color: #6b7280;
}

.excel-question-row {
  display: flex;
  gap: 12px;
  align-items: center;
  margin-bottom: 12px;
}

.excel-question-input {
  flex: 1;
}

.excel-message-tag {
  display: inline-block;
  margin-bottom: 8px;
  padding: 2px 8px;
  border-radius: 10px;
  border: 1px solid #60a5fa;
  background: #eff6ff;
  font-size: 12px;
  color: #1d4ed8;
}

.empty-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  height: 100%;
  gap: 12px;
  text-align: center;
  color: #4b5563;
}

.empty-state h2 {
  margin: 0;
  font-size: 22px;
  color: #1f2937;
}

.suggestions {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 12px;
  width: 100%;
  max-width: 640px;
}

.suggestion-card {
  border: 1px solid #d1d5db;
  border-radius: 12px;
  padding: 14px 16px;
  background: #ffffff;
  cursor: pointer;
  text-align: left;
  font-size: 14px;
  color: #1f2937;
  transition: transform 0.2s ease, box-shadow 0.2s ease;
}

.suggestion-card:hover {
  transform: translateY(-2px);
  box-shadow: 0 12px 24px rgba(15, 23, 42, 0.08);
}

.message-row {
  display: flex;
  gap: 18px;
  padding: 24px 32px;
  border-bottom: 1px solid #edeff2;
  background: #ffffff;
}

.message-row.message-assistant {
  background: #f7f7f8;
}

.message-row.message-user {
  flex-direction: row-reverse;
  background: #ffffff;
}

.message-avatar {
  width: 40px;
  height: 40px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 600;
  font-size: 14px;
  color: #ffffff;
  background: #10a37f;
  flex-shrink: 0;
}

.message-row.message-assistant .message-avatar {
  background: #acb2be;
  color: #1f2933;
}

.message-content {
  max-width: 720px;
  line-height: 1.7;
  color: #1f2933;
  font-size: 15px;
  word-break: break-word;
}

.message-placeholder {
  padding: 14px 16px;
  background: #eef2ff;
  border-radius: 10px;
  font-size: 14px;
  color: #4b5563;
}

.streaming-placeholder-row {
  opacity: 0.8;
}

.typing-placeholder {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  color: #6b7280;
}
.typing-dots {
  display: inline-flex;
  align-items: center;
  gap: 4px;
}

.typing-dots span {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: #6b7280;
  animation: typingBounce 1.2s infinite ease-in-out;
}

.typing-dots span:nth-child(2) {
  animation-delay: 0.15s;
}

.typing-dots span:nth-child(3) {
  animation-delay: 0.3s;
}

@keyframes typingBounce {
  0%,
  80%,
  100% {
    transform: translateY(0);
    opacity: 0.4;
  }
  40% {
    transform: translateY(-3px);
    opacity: 1;
  }
}

.typing-placeholder::after {
  content: "";
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: #9ca3af;
  animation: typing-blink 1.2s infinite ease-in-out;
}

@keyframes typing-blink {
  0% {
    opacity: 0.3;
    transform: scale(0.8);
  }
  50% {
    opacity: 1;
    transform: scale(1);
  }
  100% {
    opacity: 0.3;
    transform: scale(0.8);
  }
}

.rag-summary {
  margin-top: 10px;
  font-size: 12px;
  color: #6b7280;
}

.message-content pre {
  background: #1e1e1e;
  color: #f9fafb;
  padding: 14px 16px;
  border-radius: 12px;
  overflow-x: auto;
  font-family: "Fira Code", "Consolas", monospace;
  font-size: 13px;
}

.message-content code {
  background: rgba(15, 23, 42, 0.08);
  padding: 2px 6px;
  border-radius: 6px;
  font-family: "Fira Code", "Consolas", monospace;
  font-size: 13px;
}

.message-content a {
  color: #2563eb;
  text-decoration: none;
}

.message-content a:hover {
  text-decoration: underline;
}

.chatgpt-composer {
  border-top: 1px solid #e5e7eb;
  padding: 16px 24px 20px;
  background: linear-gradient(
    180deg,
    rgba(255, 255, 255, 0.85) 0%,
    #ffffff 60%
  );
}

.composer-box {
  background: #ffffff;
  border: 1px solid #d1d5db;
  border-radius: 16px;
  padding: 12px 16px;
  display: flex;
  flex-direction: column;
  gap: 12px;
  box-shadow: 0 12px 24px rgba(15, 23, 42, 0.06);
}

.composer-input {
  border: none;
}

:deep(.composer-input .el-textarea__inner) {
  border: none;
  box-shadow: none;
  padding: 0;
  font-size: 15px;
  line-height: 1.6;
}

.composer-actions {
  display: flex;
  justify-content: space-between;
  align-items: center;
  font-size: 12px;
  color: #6b7280;
}

.composer-buttons {
  display: flex;
  gap: 8px;
}

.composer-disclaimer {
  margin: 12px 4px 0;
  font-size: 12px;
  color: #9ca3af;
  text-align: center;
}

.chat-data-lab-drawer {
  --el-drawer-padding-primary: 0;
}

.drawer-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
}

.drawer-header__actions {
  display: flex;
  gap: 8px;
}

.dataset-select {
  min-width: 220px;
}

.dataset-name {
  margin-left: 8px;
  color: #6b7280;
}

.chart-preview {
  width: 100%;
  height: 320px;
  margin-top: 12px;
  background: #f9fafb;
  border: 1px dashed #e5e7eb;
  border-radius: 8px;
}

.chart-preview.placeholder {
  display: flex;
  align-items: center;
  justify-content: center;
  color: #9ca3af;
}

@media (max-width: 960px) {
  .chatgpt-layout {
    flex-direction: column;
  }

  .chatgpt-sidebar {
    width: 80%;
    max-width: 320px;
    padding: 16px;
    border-right: none;
    border-bottom: none;
    position: fixed;
    top: 0;
    left: 0;
    bottom: 0;
    z-index: 20;
    overflow-y: auto;
  }

  .conversation-list {
    max-height: calc(100vh - 220px);
  }

  .chatgpt-header {
    padding: 16px;
  }

  .chatgpt-messages {
    padding-bottom: 80px;
  }

  .chatgpt-composer {
    padding: 12px 16px;
  }

  .message-row {
    padding: 16px 20px;
  }

  .message-content {
    max-width: calc(100% - 60px);
    font-size: 14px;
  }

  .excel-panel {
    padding: 12px 20px 0;
  }

  .excel-status-row {
    flex-direction: column;
    align-items: flex-start;
    gap: 8px;
  }

  .excel-question-row {
    flex-direction: column;
    gap: 8px;
  }

  .suggestions {
    grid-template-columns: 1fr;
    max-width: 100%;
    padding: 0 20px;
  }
}

@media (max-width: 640px) {
  .chatgpt-sidebar {
    width: 100%;
    max-width: 100%;
  }

  .message-row {
    padding: 12px 16px;
    gap: 12px;
  }

  .message-avatar {
    width: 32px;
    height: 32px;
    font-size: 12px;
  }

  .message-content {
    max-width: calc(100% - 50px);
    font-size: 14px;
    line-height: 1.5;
  }

  .message-content pre {
    padding: 10px 12px;
    font-size: 12px;
  }

  .message-content code {
    padding: 1px 4px;
    font-size: 12px;
  }

  .chatgpt-header {
    padding: 12px 16px;
  }

  .header-titles h1 {
    font-size: 18px;
    margin: 0;
  }

  .header-actions {
    margin-top: 8px;
  }

  .model-select {
    min-width: 140px;
  }

  .excel-panel {
    padding: 12px 16px 0;
  }

  .excel-status-text {
    font-size: 13px;
  }

  .excel-panel-tip {
    font-size: 11px;
  }

  .chatgpt-composer {
    padding: 12px 16px;
  }

  .composer-box {
    padding: 10px 14px;
  }

  :deep(.composer-input .el-textarea__inner) {
    font-size: 14px;
  }

  .composer-actions {
    font-size: 11px;
  }

  .composer-disclaimer {
    font-size: 11px;
  }

  .suggestion-card {
    padding: 12px 14px;
    font-size: 13px;
  }

  .empty-state h2 {
    font-size: 18px;
  }

  .empty-state p {
    font-size: 14px;
  }
}
</style>
